(function(root) {
    function _(obj) {
        //  若 obj已经是 _的实例则直接返回obj
        if (obj instanceof _) {
            return obj
        }

        if (!(this instanceof _)) {
            return new _(obj)
        }
        // 将原始数据存储在 _wrapped 属性内部
        this._wrapped = obj

    }
    _.prototype.value = function() {
        return this._wrapped
    }
    _.unique = function(arr, callback) {

            var ret = [];
            var target, i = 0;
            for (; i < arr.length; i++) {
                target = callback ? callback(arr[i]) : arr[i]
                if (ret.indexOf(target) === -1) {
                    ret.push(target)
                }
            }

            return ret
        }
        //  拷贝对象的属性名称
    _.functions = function(obj) {
        var result = [];
        for (var key in obj) {
            result.push(key)
        }
        return result
    }
    _.isArray = function(array) {
        return Object.prototype.toString.call(array).slice(8, -1) === 'Array'
    }

    //  add _chain of prototype for object to adjust open chain
    _.chain = function(obj) {

        var instance = _(obj)

        instance._chain = true;
        return instance

    }

    _.map = function(obj, iteratee, context) {
            var iteratee = cb(iteratee, context)
            var keys = !_.isArray(obj) && Object.keys(obj);
            var length = (keys || obj).length;
            var index = 0;
            var result = Array(length)
            for (; index < length; index++) {
                // judge key to iterate Object or Array
                var target = keys ? keys[index] : index
                result[target] = iteratee(obj[target], target, obj)
            }
            return result

        }
        /**
         * @description create new iteratee
         * @param {*} iteratee 
         * @param {*} context 
         */
    function cb(iteratee, context, count) {

        if (iteratee == null) {
            return _.dfIterator
        }

        if (_.isFunction(iteratee)) {
            return optimizeCb(iteratee, context, count)
        }
    }

    _.isFunction = function(func) {
        return Object.prototype.toString.call(func).slice(8, -1) === 'Function'
    }

    function optimizeCb(iteratee, context, count) {
        if (context == void(0)) {
            return iteratee
        }

        switch (count == 1 ? count : 3) {
            case 1:
                return function(value) {
                    iteratee.call(context, value)
                }

            case 3:
                return function(value, index, obj) {

                    iteratee.call(context, value, index, obj)
                }

        }

    }

    function dfIterator(value) {
        return value
    }
    _.each = function(target, callback) {
        if (_.isArray(target)) {
            for (let i = 0; i < target.length; i++) {
                callback.call(target, target[i], i)
            }
        } else {
            for (const key in target) {
                callback.call(target, target[key], key)
            }
        }
    }
    var result = function(instance, obj) {
            return instance._chain ? _(obj).chain() : obj
        }
        // combine something to _
    _.mixin = function(obj) {
        _.each(_.functions(obj), function(name) {
            var func = obj[name] //  [result ,each ... ];
            _.prototype[name] = function() {
                var args = [this._wrapped];
                Array.prototype.push(args, arguments)
                    //args [this._wrapped , callback()]

                return result(this, func.apply(this, args))
            }
        })
    }
    _.mixin(_)
    root._ = _
})(this)